package com.flanz.apache.velocity.uimocker;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Simple HTTP 1.0 WebServer.
 * 
 * @author cf
 * 
 */

public class WebServer {

	private static Logger LOG = LoggerFactory.getLogger(WebServer.class);
	private static boolean useThreadPoolExecutor = true;
	private static boolean useNonBlockingMode = true;

	/**
	 * @param args
	 * @throws IOException
	 */
	public static void main(final String[] args) throws IOException {

		StartUpConfig startUpConfig = new StartUpConfig(args);

		ProjectConfig projectConfig = createProjectConfig(startUpConfig);

		if (useNonBlockingMode) {
			WebServer.startNB(projectConfig);

		} else {
			WebServer.start(projectConfig);
		}
	}

	/**
	 * creates project config.
	 * 
	 * @param startUpConfig
	 * @return
	 * @throws FileNotFoundException
	 * @throws IOException
	 */

	private static ProjectConfig createProjectConfig(
			final StartUpConfig startUpConfig) throws FileNotFoundException,
			IOException {
		MimeTable mimeTable = new MimeTable(
				startUpConfig.getMandatoryString(StartUpConfig.MIME_MAPPING));

		Configuration APP = null;
		try {
			APP = new PropertiesConfiguration(
					startUpConfig.getMandatoryString(StartUpConfig.APP_VARS));
		} catch (ConfigurationException e) {
			LOG.error("Configuration exception!", e);
		}

		String projectDir = startUpConfig
				.getMandatoryString(StartUpConfig.PROJECT_DIR);
		String projectDocRoot = startUpConfig
				.getMandatoryString(StartUpConfig.PROJECT_DOCROOT);
		int serverPort = startUpConfig.getInteger(StartUpConfig.SERVER_PORT,
				StartUpConfig.SERVER_PORT_DEFAULT);
		int maxThread = startUpConfig.getInteger(
				StartUpConfig.MAX_REQUEST_THREADS,
				ProjectConfig.UNLIMITED_REQUEST_THREAD);

		String projectMacrosFileList = startUpConfig.getString(
				StartUpConfig.PROJECT_GLOBALMACROS_FILELIST, "");

		ProjectConfig projectConfig = new ProjectConfig();
		projectConfig.setServerPort(serverPort);
		projectConfig.setVM_APP_VARS(APP);
		projectConfig.setProjectDir(projectDir);
		projectConfig.setMimeTable(mimeTable);
		projectConfig.setHttpDocRoot(projectDocRoot);
		projectConfig.setMaxRequestThread(maxThread);
		projectConfig.setProjectMacrosFileList(projectMacrosFileList);

		return projectConfig;
	}

	/**
	 * Start WebServer non blocking. 1 Thread per Request.
	 */

	protected static void startNB(final ProjectConfig projectConfig) {
		// Listen for incoming connections and handle them
		try {
			ServerSocket listener = startUpSocketBindings(projectConfig);
			int maxRequestThread = projectConfig.getMaxRequestThread();

			// Create the ThreadPoolExecutor
			BlockingQueue<Runnable> worksQueue = new ArrayBlockingQueue<Runnable>(
					maxRequestThread);
			MyRejectedExecutionHandler myRejectedExecutionHandler = new MyRejectedExecutionHandler();

			ThreadPoolExecutor executor = null;
			if (useThreadPoolExecutor) {
				executor = new ThreadPoolExecutor(maxRequestThread,
						maxRequestThread, 10, TimeUnit.SECONDS, worksQueue,
						myRejectedExecutionHandler);
			}

			for (; listener != null;) {
				if ((WebServerThread.getConnectionCount() < maxRequestThread)
						|| (maxRequestThread == 0)) {
					Socket acceptSocket = listener.accept();
					WebServerThread connection = new WebServerThread(
							acceptSocket, projectConfig);

					if (useThreadPoolExecutor) {
						WebServerThread.threadStart();
						executor.execute(connection);
					} else {
						Thread t = new Thread(connection);
						WebServerThread.threadStart();
						t.start();
					}

				} else {
					try {
						Thread.sleep(200);
					} catch (InterruptedException e) {
						LOG.error("Thread interrupted!", e);
					}
				}
			}
		} catch (IOException ioe) {
			LOG.error("IOException on socket listen!", ioe);
		}
	}

	/**
	 * Blocking WebServer use Process Thread.
	 * 
	 * @param projectConfig
	 */

	protected static void start(final ProjectConfig projectConfig) {
		ServerSocket s = startUpSocketBindings(projectConfig);

		for (; s != null;) {
			try {

				Socket accept = s.accept();

				InputStream inputStream = accept.getInputStream();
				OutputStream outputStream = accept.getOutputStream();

				HTTPRequestHandler.handleSocketConnection(
						projectConfig.getVM_APP_VARS(),
						projectConfig.getMimeTable(),
						projectConfig.getProjectDir(),
						projectConfig.getHttpDocRoot(),
						projectConfig.getProjectMacrosFileList(), inputStream,
						outputStream);

				accept.close();
			} catch (Exception e) {
				LOG.error("cannot start socket!", e);
			}
		}
	}

	private static ServerSocket startUpSocketBindings(
			final ProjectConfig projectConfig) {
		ServerSocket result;

		LOG.info("Webserver starting up on port "
				+ projectConfig.getServerPort());
		LOG.info("(press ctrl-c to exit)");

		try {
			// create the main server socket
			result = new ServerSocket(projectConfig.getServerPort());
		} catch (Exception e) {
			LOG.error("cannot create server socket!", e);
			return null;
		}

		LOG.info("Waiting for connection");
		return result;
	}

}
